﻿/********************************************************************
	created:	2019/07/25
	created:	25:7:2019   23:25
	filename: 	D:\MaYun\MiniGame\LogicCenter\Tools\Base\HotFix.cs
	file path:	D:\MaYun\MiniGame\LogicCenter\Tools\Base
	file base:	HotFix
	file ext:	cs
	author:		ShanYong
	
	purpose:	Hook
*********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Tools.Base
{
    public static class HotFixUtil
    {
        public static int Fix(MethodInfo rawMethod, MethodInfo hookMethod, PactchSelect ps = PactchSelect.Method1)
        {
            if (null == rawMethod || null == hookMethod)
                return HotFixErrCode.ErrMethod;

            if (!CheckMethodSignature(rawMethod, hookMethod))
                return HotFixErrCode.ErrSig;

            // 保证都是JIT过的函数
            var rawTypeHandle = rawMethod.DeclaringType.GetGenericArguments().Select(t => t.TypeHandle).ToArray();
            RuntimeHelpers.PrepareMethod(rawMethod.MethodHandle, rawTypeHandle);

            var hookTypeHandle = hookMethod.DeclaringType.GetGenericArguments().Select(t => t.TypeHandle).ToArray();
            RuntimeHelpers.PrepareMethod(hookMethod.MethodHandle, hookTypeHandle);

            if(PactchSelect.Method1 == ps)
            {
                return PathMethod1(rawMethod, hookMethod);
            }
            else
            {
                return PathMethod2(rawMethod, hookMethod);
            }
        }

        /// <summary>
        /// 方法一原理
        /// JIT后，指向函数的指令Call变为JMP
        /// JMP为5个字节，后4个位偏移量
        /// 计算出偏移量 即可知道函数的位置，通过函数的位置即可计算出和要hook的函数的偏移位置
        /// 修改偏移位置即可
        /// </summary>
        /// <param name="rawMethod"></param>
        /// <param name="hookMethod"></param>
        /// <returns></returns>
        private static int PathMethod1(MethodInfo rawMethod, MethodInfo hookMethod)
        {
            unsafe
            {
                byte* hookPtr = (byte*)hookMethod.MethodHandle.GetFunctionPointer().ToPointer();

                // 不是JMP 不能使用这个方法
                if(*hookPtr != 0xE9)
                {
                    return HotFixErrCode.ErrSys1;
                }

                var hookMethodPtr = (ulong)hookPtr + *(uint*)(hookPtr + 1);

                byte* rawPtr = (byte*)rawMethod.MethodHandle.GetFunctionPointer().ToPointer();

                if(*rawPtr != 0xE9)
                {
                    return HotFixErrCode.ErrSys2;
                }

                // 计算偏移位置
                uint offSet = (uint)(hookMethodPtr - (ulong)rawPtr);

                // 系统方法没有写权限，需要修改页属性
                uint oldProtect1;
                NativeAPI.VirtualProtect((IntPtr)(rawPtr + 1), 4, Protection.PAGE_EXECUTE_READWRITE, out oldProtect1);
                // 修改内存数值
                *(uint*)(rawPtr + 1) = offSet;
            }

            return HotFixErrCode.Sucess;
        }

        /// <summary>
        /// 方法二原理
        /// 直接在函数的入口执行跳转指令
        /// 因为函数入口本来就是跳转指令
        /// 所以和一的区别就是执行了两次跳转（jmp）
        /// </summary>
        /// <param name="rawMethod"></param>
        /// <param name="hookMethod"></param>
        /// <returns></returns>
        private static int PathMethod2(MethodInfo rawMethod, MethodInfo hookMethod)
        {
            unsafe
            {
                var rawMethodPtr = (byte*)rawMethod.MethodHandle.GetFunctionPointer().ToPointer();
                var hookMethodPtr = (byte*)hookMethod.MethodHandle.GetFunctionPointer().ToPointer();

                byte[] newInstrs = { 0xE9, 0x90, 0x90, 0x90, 0x90 };
                //生成跳转指令，使用相对地址，用于跳转到用户定义函数
                fixed (byte* newInstrPtr = newInstrs)
                {
                    *(uint*)(newInstrPtr + 1) = (uint)((ulong)hookMethodPtr - (ulong)rawMethodPtr - 5);
                }

                uint oldProtect;
                // 系统方法没有写权限，需要修改页属性
                NativeAPI.VirtualProtect((IntPtr)rawMethodPtr, 5, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);
                for(int i = 0; i< newInstrs.Length; i++)
                {
                    *(rawMethodPtr + i) = newInstrs[i];
                }
            }
            return HotFixErrCode.Sucess;
        }

        private static bool CheckMethodSignature(MethodBase x, MethodBase y)
        {
            if (null == x || null == y)
                return false;

            if (x.CallingConvention != y.CallingConvention)
                return false;

            if(x.IsStatic != y.IsStatic)
                return false;

            // 返回值检查
            Type returnX = GetMethodReturnType(x);
            Type returnY = GetMethodReturnType(y);
            if (returnX != returnY)
                return false;

            // 参数个数检查
            ParameterInfo[] xParams = x.GetParameters();
            ParameterInfo[] yParams = y.GetParameters();

            if (null == xParams)
                return false;

            if (null == yParams)
                return false;

            // 参数长度检查
            if (xParams.Length != yParams.Length)
                return false;

            // 参数类型检查
            for(int i = 0; i < xParams.Length; i++)
            {
                if (xParams[i].ParameterType != yParams[i].ParameterType)
                    return false;
            }

            return true;

        }

        /// <summary>
        /// 获取函数的返回类型
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        private static Type GetMethodReturnType(MethodBase method)
        {
            MethodInfo methodInfo = method as MethodInfo;
            if(methodInfo == null)
            {
                throw new ArgumentException("Unsupported MethodBase : " + method.GetType().Name, "method");
            }
            return methodInfo.ReturnType;
        }

 
    }

    enum Protection
    {
        PAGE_NOACCESS = 0x01,
        PAGE_READONLY = 0x02,
        PAGE_READWRITE = 0x04,
        PAGE_WRITECOPY = 0x08,
        PAGE_EXECUTE = 0x10,
        PAGE_EXECUTE_READ = 0x20,
        PAGE_EXECUTE_READWRITE = 0x40,
        PAGE_EXECUTE_WRITECOPY = 0x80,
        PAGE_GUARD = 0x100,
        PAGE_NOCACHE = 0x200,
        PAGE_WRITECOMBINGE = 0x400,

    }

    class NativeAPI
    {
        [DllImport("kernel32")]
        public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out uint lpflOldProtect);
    }

    public class HotFixErrCode
    {
        public const int Sucess = 0;
        public const int ErrSig = -1;
        public const int ErrSys1 = -2;
        public const int ErrUnknow = -3;
        public const int ErrMethod = -4;
        public const int ErrSys2 = -5;
    }

    public enum PactchSelect
    {
        Method1,
        Method2,
    }
}
